#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include <object.h>
#include <object_table.h>
#include <debug.h>




/**
 * Allocate a new object of the given type.
 *
 * This function uses information from registered object types only.
 *
 * @param type a type registed in object_table.
 *
 * @return a new object. It's casted down to object_t.
 */
object_t *
object_new( uint16_t type )
{
  object_t *obj = NULL;

  if ( type == OBJECT_TYPE_UNDEFINED )
    err( "Cannot create an undefined object!\n" );
  else
    {
      struct object_table_entry *e = get_object_table_entry( type );
      if ( e != NULL )
	{
	  if ( e->ops.new != NULL )
	    {
	      obj = e->ops.new();
	      if ( obj != NULL )
		assert( obj->type == type );
	    }
	  else
	    err( "Object is known, but unable to create one!\n" );
	}
      else
	err( "Object type %d is unknown\n", type );
    }

  return obj;
}

/**
 * Free memory allocated to object.
 *
 * @param object an allocated object.
 */
void
__object_free( object_t *object )
{
  assert( object != NULL );

  if ( object->type == OBJECT_TYPE_UNDEFINED )
    err( "Cannot free an undefined object! (This is a bug somewhere!)\n" );
  else
    {
      struct object_table_entry *e = get_object_table_entry( object->type );
      if ( e != NULL )
	{
	  if ( e->ops.free != NULL )
	    e->ops.free( object );
	  else
	    err( "Object is known, but unable to free it!\n" );
	}
      else
	err( "Object type %d is unknown\n", object->type );
    }
}

/**
 * Initialize object.
 *
 * @param object object to act on.
 * @param color object color.
 * @param transp transparency value, between 0.0 and 1.0
 * @param depth object depth in scene.
 * @param draw_opts OR'ed flags from DRAW_*, like DRAW_ANTIALIAS, DRAW_FILL.
 */
void
__object_init( object_t *object, color_t color, float transp, uint16_t depth,
	       uint8_t draw_opts )
{
  assert( object != NULL );
  object->color = color;
  object->transp = transp;
  object->depth = depth;
  object->draw_opts = draw_opts;
}

/**
 * Return the string representation of this object.
 *
 * @note string is malloc()ated, please free() it later.
 *
 * @param obj the object to act on.
 *
 * @return string representation.
 */
char *
__object_str( object_t *object )
{
  char *s = NULL;
  int r = 0;

  if ( object == NULL )
    r = asprintf( &s, "Object is NULL!" );
  else
    {
      struct object_table_entry *t = get_object_table_entry( object->type );

      if ( t == NULL || t->ops.str == NULL )
	r = asprintf( &s,
		      "Object( type=%d, depth=%d, color=0x%08x, "
		      "transp=%0.3f )",
		      object->type, object->depth,
		      COLOR_TO_ARGB( object->color ), object->transp );
      else
	s = t->ops.str( object );
    }

  if ( r < 0 )
    s = NULL;

  return s;
}



/**
 * Draw object on surface
 *
 * @param object an allocated object.
 * @param surface an existing surface.
 */
void
__object_draw( object_t *object, surface_t *surface )
{
  assert( object != NULL );

  if ( object->type == OBJECT_TYPE_UNDEFINED )
    err( "Cannot draw an undefined object! (This is a bug somewhere!)\n" );
  else
    {
      struct object_table_entry *e = get_object_table_entry( object->type );
      if ( e != NULL )
	{
	  if ( e->ops.draw != NULL )
	    e->ops.draw( object, surface );
	  else
	    err( "Object is known, but unable to draw it!\n" );
	}
      else
	err( "Object type %d is unknown\n", object->type );
    }
}
